#include <dirent.h>
#include <rid.h>
#include <stdint.h>
#include <stdlib.h>

#define STYLE_9
#include <fn85.h>
#include <rid_fn85.h>

#include <unfy/unfy.h>
#include <unfy/unfp.h>
#include <rls/rls.h>
#include <rls/rlsp.h>

#define NIT_SHORT_NAMES
#include <nit/list.h>
#include <nit/set.h>
#include <nit/map.h>
#include <nit/err.h>
#include <nit/file.h>
#include <nit/buf.h>
#include <nit/gap.h>

#include "utilize.h"
#include "examine.h"
#include "front.h"

/* More */

static Ilk_more *
ilk_more(Ilk_lib *lib, uint64_t srch_cnt, uint16_t act)
{
	Ilk_more *more = lib->old_mores;

	if (more) {
		lib->old_mores = *(Ilk_more **) more;
	} else if (!(more = malloc(sizeof(*more)))) {
		err_std(&lib->err);
		return NULL;
	}

	more->srch_cnt = srch_cnt;
	more->act = act;
	return more;
}

static void
more_free(void *dat, void *extra)
{
	Ilk_more *more = dat;
	Ilk_sys *sys = extra;

	*(Ilk_more **) more = sys->lib->old_mores;
	sys->lib->old_mores = more;
}

static size_t
more_len(const Rls_rule *rule, void *extra)
{
	(void) rule;
	(void) extra;
	return sizeof(uint64_t) + sizeof(uint16_t);
}

static size_t
more_write(const Rls_rule *rule, unsigned char *buf, void *extra)
{
	Ilk_more *more = rule->dat;

	(void) buf;
	(void) extra;
	*(uint64_t *) buf = htole64(more->srch_cnt);
	buf += sizeof(uint64_t);
	*(uint16_t *) buf = htole16(more->act);
	return sizeof(uint64_t) + sizeof(uint16_t);
}

static int
act_set(Ilk_sys *sys, Rls_rule *rule, uint16_t act_num, size_t pos)
{
	if (act_num > sys->lib->act_dat.max_num) {
		err_cust(&sys->lib->err,
			 "Invalid actable function number from deser");
		err_pos(&sys->lib->err, pos);
		return -1;
	}

	rule->u.act = sys->lib->act_dat.table[act_num];
	return 0;
}

static int
more_deser(Rls_rule *rule, const unsigned char *buf, size_t *pos, void *extra)
{
	Ilk_sys *sys = extra;
	Ilk_more *more = malloc(sizeof(*more));
	Ilk_rule_path path;

	if (!more) {
		err_std(&sys->lib->err);
		err_pos(&sys->lib->err, *pos);
		goto err_more;
	}


	more->srch_cnt = le64toh(*(uint64_t *) buf);
	*pos += sizeof(uint64_t);
	buf += sizeof(uint64_t);
	more->act = le16toh(*(uint16_t *) buf);

	if (more->act &&
	    act_set(sys, rule, more->act, *pos) < 0) {
		free(more);
		goto err_act;
	}

	*pos += sizeof(uint16_t);
	rule->dat = more;
	return 0;
err_act:
	free(more);
	ilk_rule_path(path, rule->id);
	err_where(&sys->lib->err, path);
err_more:
	return -1;

}

static const char *
more_deserable(const unsigned char *buf, size_t *pos, size_t max, void *extra)
{
	(void) buf;
	(void) extra;

	if ((*pos += sizeof(uint64_t) + sizeof(uint16_t)) > max) {
		*pos = max;
		return "Not enough space for moredata";
	}

	return NULL;
}

/* Core code */

static Rls_rule **
ilk_bend(const Unfy_term *term, Unfy_stat *stat, void *extra)
{
	Ilk_grp *grp = ilk_grp_get_term(extra, term, stat);

	if (!grp)
		return NULL;

	return &grp->rules;
}

static int
load_ilksys_file(Ilk_sys *sys)
{
	Nit_err tmp;
	Nit_file file;

	if (file_open(&file, &sys->dir, "Ilksys", &sys->lib->err) < 0)
		goto err_not_open;

	if ((size_t) file.len < sizeof(sys->srch_cnt)) {
		err_cust(&sys->lib->err,
			 "Ilksys file invalid, too small for search count");
		goto err_file;
	}

	if (file_content(&file, &sys->lib->buf.bytes,
			 &sys->lib->buf.size, &sys->lib->err) < 0)
		goto err_file;

	sys->srch_cnt = le64toh(*(uint64_t *) sys->lib->buf.bytes);

	if (file_close(&file, &sys->lib->err))
		goto err_not_open;

	return 0;
err_file:
	file_close(&file, &tmp);
err_not_open:
	return -1;
}

static void
srlz_init(Rlsp_srlz *srlz)
{
	srlz->dat_free = more_free;
	srlz->len = more_len;
	srlz->write = more_write;
	srlz->deser = more_deser;
	srlz->deserable = more_deserable;
}

static void
act_dat_init(Ilk_act_dat *act_dat, uint16_t max_num, Rls_act *table)
{
	act_dat->max_num = max_num;
	act_dat->table = table;
}

void
ilk_lib_init(Ilk_lib *lib, uint16_t act_max, Rls_act *act_table)
{
	act_dat_init(&lib->act_dat, act_max, act_table);
	lib->bend = ilk_bend;
	srlz_init(&lib->srlz);
	lib->old_grps = NULL;
        lib->old_mores = NULL;
	lib->old_syss = NULL;
	rls_recycle_init(&lib->rec);
        lib->stack = NULL;
	buf_init(&lib->buf, 0);
	err_init(&lib->err);
        lib->testing = 0;
}

void
ilk_lib_dispose(Ilk_lib *lib)
{
	while (lib->old_grps) {
		Ilk_grp *tmp = lib->old_grps;

	        lib->old_grps = (Ilk_grp *) tmp->rules;
		free(tmp);
	}

	while (lib->old_mores) {
		Ilk_more *tmp = lib->old_mores;

	        lib->old_mores = *(Ilk_more **) tmp;
		free(tmp);
	}

	while (lib->old_syss) {
		Ilk_sys *tmp = lib->old_syss;

	        lib->old_syss = (Ilk_sys *) tmp->lib;
		free(tmp);
	}

	buf_dispose(&lib->buf);
	entries_free(lib->stack);
	rls_recycle_empty(&lib->rec);
	err_dispose(&lib->err);
}

int
ilk_sys_init(Ilk_sys *sys, Ilk_lib *lib, const char *dir_name)
{
	sys->lib = lib;
	sys->srch_cnt = 0;;

	if (dir_init(&sys->dir, dir_name, &sys->lib->err))
		return -1;

	switch (file_exists(&sys->dir, "Ilksys", &sys->lib->err)) {
	case 1:
		if (load_ilksys_file(sys) < 0)
			return -1;
		/* fallthru */
	case 0:
		break;
	default:
		goto err_dir;
	}

	if (map_init(&sys->grps, 0) < 0) {
		err_cust(&sys->lib->err, "Failed to init grps");
		goto err_dir;
	}

	if (map_init(&sys->srchs, 0) < 0) {
		err_cust(&sys->lib->err, "Failed to init srchs");
		goto err_srchs;
	}

	return 0;
err_srchs:
	map_dispose(&sys->grps);
err_dir:
	dir_dispose(&sys->dir);
	return -1;
}

static void
grp_free(Ilk_grp *grp, Ilk_sys *sys)
{
	ilk_grp_dispose(grp, sys);
	free(grp);
}

static void
sys_grps_free(void *key, void *val, void *extra)
{
	(void) key;
	grp_free(val, extra);
}

static void
sys_srchs_free(void *key, void *val, void *extra)
{
	Ilk_sys *sys = extra;

	(void) key;
	rls_recycle_srch(&sys->lib->rec, val);
}

void
ilk_sys_dispose(Ilk_sys *sys)
{
	map_empty(&sys->grps, sys_grps_free, sys);
	map_empty(&sys->srchs, sys_srchs_free, sys);
	map_dispose(&sys->grps);
	map_dispose(&sys->srchs);
	dir_dispose(&sys->dir);
}

Ilk_sys *
ilk_sys(Ilk_lib *lib, const char *dir_name)
{
	Ilk_sys *sys = lib->old_syss;

	if (sys) {
		lib->old_syss = (Ilk_sys *) sys->lib;
	} else if (!(sys = malloc(sizeof(*sys)))) {
		err_std(&lib->err);
		err_where(&lib->err, "sys");
		return NULL;
	}

	if (ilk_sys_init(sys, lib, dir_name) < 0) {
		free(sys);
		return NULL;
	}

	return sys;
}

void
ilk_sys_rec(Ilk_sys *sys)
{
	Ilk_lib *lib = sys->lib;

	ilk_sys_dispose(sys);
	sys->lib = (Ilk_lib *) lib->old_syss;
        lib->old_syss = sys;
}

static int
save_ilksys_file(Ilk_sys *sys)
{
	Nit_err tmp;
	Nit_file file;
	uint64_t save_cnt = htole64(sys->srch_cnt);

	if (file_open(&file, &sys->dir, "Ilksys", &sys->lib->err) < 0)
		goto err_no_close;

	if (file_set(&file, &save_cnt, sizeof(save_cnt), &sys->lib->err) < 0)
		goto err_close;

	if (file_close(&file, &sys->lib->err) < 0)
		goto err_no_close;

	return 0;
err_close:
	file_close(&file, &tmp);
err_no_close:
	return -1;
}

int
ilk_sys_save(Ilk_sys *sys)
{
	Nit_iter iter;

	if (save_ilksys_file(sys) < 0)
		return -1;

	if (!sys->grps.entry_num)
		goto srchs;

	iter_init(&iter, &sys->grps);

	do
		if (ilk_grp_save(iter_val(&iter), sys) < 0)
			return -1;
	while (iter_next(&iter));

srchs:

	if (!sys->srchs.entry_num)
		goto end;

	iter_init(&iter, &sys->srchs);

	do {
		uint32_t size;
		uint64_t srch_cnt = *(uint64_t *) iter_key(&iter, &size);

	        rlsp_srch_print(iter_val(&iter), 1);

		if (ilk_srch_save(sys, iter_val(&iter), srch_cnt) < 0)
			return -1;
	} while (iter_next(&iter));

end:
	return 0;
}

const char *
ilk_rule_valid(const Unfy_term *head, const Unfy_list *body)
{
	static const char *empty_list = "Rule head cannot be an empty list";
	static const char *no_const = "Rule head list must start with const";
	static const char *invalid_type = "Rule head has invalid type";
	const char *error;

	if (!head)
		return 0;

	switch (head->type) {
	case UNFY_CONST:
	        break;
	case UNFY_LIST:
		if (!head->u.list)
			return empty_list;

		if (head->u.list->term->type != UNFY_CONST)
			return no_const;

	        break;
	default:
	        return invalid_type;
	}

	for (; body; body = body->next)
		if ((error = ilk_rule_valid(body->term, NULL))) {
			if (error == empty_list)
				return "Rule body term cannot "
					"be an empty list";

			if (error == no_const)
				return "Rule body term list must "
					"start with const";

			return "Rule body term has invalid type";
		}

	return NULL;
}

Ilk_grp *
ilk_rule_add(Ilk_sys *sys, const Rid id, Unfy_term *head, Unfy_list *body,
	     uint64_t srch_cnt)
{
	const char *error;
	Ilk_more *more;
	Ilk_grp *grp;

	if ((error = ilk_rule_valid(head, body))) {
		err_cust(&sys->lib->err, error);
		return NULL;
	}

	if (!(grp = ilk_grp_get_term(sys, head, NULL)) ||
	    !(more = ilk_more(sys->lib, srch_cnt, 0)))
		return NULL;

	if (rls_rule_add(&grp->rules, id, head,
			 body, more, &sys->lib->rec) < 0) {
		err_cust(&sys->lib->err, "Failed to add rule");
		free(more);
		return NULL;
	}

	grp->changed = 1;
	grp->accessed = 1;
	return grp;
}

Ilk_grp *
ilk_act_add(Ilk_sys *sys, const Rid id, Unfy_term *head, uint16_t act_num)
{
	const char *error;
	Ilk_more *more;
	Ilk_grp *grp;

	if (!act_num || act_num > sys->lib->act_dat.max_num) {
		err_cust(&sys->lib->err, "Act num invalid");
		return NULL;
	}

	if ((error = ilk_rule_valid(head, NULL))) {
		err_cust(&sys->lib->err, error);
		return NULL;
	}

	if (!(grp = ilk_grp_get_term(sys, head, NULL)) ||
	    !(more = ilk_more(sys->lib, 0, act_num)))
		return NULL;

	if (rls_rule_add_actable(&grp->rules, id, head,
				 sys->lib->act_dat.table[act_num],
				 more, &sys->lib->rec) < 0) {
		err_cust(&sys->lib->err, "Failed to add actable rule");
		free(more);
		return NULL;
	}

	grp->changed = 1;
	grp->accessed = 1;
	return grp;
}

Unfy_stat
ilk_srch_n_save(Ilk_sys *sys, Rls_srch **srch_ref, Unfy_term *term,
		int *limit, Rls_info *info)
{
	Unfy_stat stat = ilk_srch(sys, srch_ref, term, limit, info);

	if (stat == UNFY_YES &&
	    ilk_srch_save(sys, *srch_ref, sys->srch_cnt) < 0)
		return UNFY_ERR;

	return stat;
}
